/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.editor.language.json.converter;

import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import math.geom2d.Point2D;

import math.geom2d.conic.Circle2D;

import math.geom2d.line.Line2D;

import math.geom2d.polygon.Polyline2D;

import org.activiti.bpmn.model.ActivitiListener;
import org.activiti.bpmn.model.Activity;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BooleanDataObject;
import org.activiti.bpmn.model.BoundaryEvent;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.DateDataObject;
import org.activiti.bpmn.model.DoubleDataObject;
import org.activiti.bpmn.model.EventListener;
import org.activiti.bpmn.model.FieldExtension;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowElementsContainer;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.GraphicInfo;
import org.activiti.bpmn.model.ImplementationType;
import org.activiti.bpmn.model.IntegerDataObject;
import org.activiti.bpmn.model.ItemDefinition;
import org.activiti.bpmn.model.Lane;
import org.activiti.bpmn.model.LongDataObject;
import org.activiti.bpmn.model.Pool;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.bpmn.model.StringDataObject;
import org.activiti.bpmn.model.SubProcess;
import org.activiti.bpmn.model.ValuedDataObject;

import org.activiti.editor.constants.EditorJsonConstants;
import org.activiti.editor.constants.StencilConstants;
import org.activiti.editor.language.json.converter.util.JsonConverterUtil;

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Tijs Rademakers
 */
public class BpmnJsonConverter implements EditorJsonConstants,
        StencilConstants, ActivityProcessor {
    protected static final Logger LOGGER = LoggerFactory
            .getLogger(BpmnJsonConverter.class);
    protected static Map<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>> convertersToJsonMap = new HashMap<Class<? extends BaseElement>, Class<? extends BaseBpmnJsonConverter>>();
    protected static Map<String, Class<? extends BaseBpmnJsonConverter>> convertersToBpmnMap = new HashMap<String, Class<? extends BaseBpmnJsonConverter>>();
    private static SimpleDateFormat sdf = new SimpleDateFormat(
            "yyyy-MM-dd'T'HH:mm:ss");

    static {
        // start and end events
        StartEventJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        EndEventJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // connectors
        SequenceFlowJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // task types
        BusinessRuleTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        MailTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        ManualTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        ReceiveTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        ScriptTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        ServiceTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        UserTaskJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        CallActivityJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // gateways
        ExclusiveGatewayJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        InclusiveGatewayJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        ParallelGatewayJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        EventGatewayJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // scope constructs
        SubProcessJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
        EventSubProcessJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // catch events
        CatchEventJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // throw events
        ThrowEventJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);

        // boundary events
        BoundaryEventJsonConverter.fillTypes(convertersToBpmnMap,
                convertersToJsonMap);
    }

    private static final List<String> DI_CIRCLES = new ArrayList<String>();
    private static final List<String> DI_RECTANGLES = new ArrayList<String>();
    private static final List<String> DI_GATEWAY = new ArrayList<String>();

    static {
        DI_CIRCLES.add(STENCIL_EVENT_START_ERROR);
        DI_CIRCLES.add(STENCIL_EVENT_START_MESSAGE);
        DI_CIRCLES.add(STENCIL_EVENT_START_NONE);
        DI_CIRCLES.add(STENCIL_EVENT_START_TIMER);

        DI_CIRCLES.add(STENCIL_EVENT_BOUNDARY_ERROR);
        DI_CIRCLES.add(STENCIL_EVENT_BOUNDARY_SIGNAL);
        DI_CIRCLES.add(STENCIL_EVENT_BOUNDARY_TIMER);
        DI_CIRCLES.add(STENCIL_EVENT_BOUNDARY_MESSAGE);

        DI_CIRCLES.add(STENCIL_EVENT_CATCH_MESSAGE);
        DI_CIRCLES.add(STENCIL_EVENT_CATCH_SIGNAL);
        DI_CIRCLES.add(STENCIL_EVENT_CATCH_TIMER);

        DI_CIRCLES.add(STENCIL_EVENT_THROW_NONE);
        DI_CIRCLES.add(STENCIL_EVENT_THROW_SIGNAL);

        DI_CIRCLES.add(STENCIL_EVENT_END_NONE);
        DI_CIRCLES.add(STENCIL_EVENT_END_ERROR);

        DI_RECTANGLES.add(STENCIL_CALL_ACTIVITY);
        DI_RECTANGLES.add(STENCIL_SUB_PROCESS);
        DI_RECTANGLES.add(STENCIL_EVENT_SUB_PROCESS);
        DI_RECTANGLES.add(STENCIL_TASK_BUSINESS_RULE);
        DI_RECTANGLES.add(STENCIL_TASK_MAIL);
        DI_RECTANGLES.add(STENCIL_TASK_MANUAL);
        DI_RECTANGLES.add(STENCIL_TASK_RECEIVE);
        DI_RECTANGLES.add(STENCIL_TASK_SCRIPT);
        DI_RECTANGLES.add(STENCIL_TASK_SERVICE);
        DI_RECTANGLES.add(STENCIL_TASK_USER);

        DI_GATEWAY.add(STENCIL_GATEWAY_EVENT);
        DI_GATEWAY.add(STENCIL_GATEWAY_EXCLUSIVE);
        DI_GATEWAY.add(STENCIL_GATEWAY_INCLUSIVE);
        DI_GATEWAY.add(STENCIL_GATEWAY_PARALLEL);
    }

    protected ObjectMapper objectMapper = new ObjectMapper();

    public ObjectNode convertToJson(BpmnModel model) {
        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put("bounds",
                BpmnJsonConverterUtil.createBoundsNode(1485, 1050, 0, 0));
        modelNode.put("resourceId", "canvas");

        ObjectNode stencilNode = objectMapper.createObjectNode();
        stencilNode.put("id", "BPMNDiagram");
        modelNode.put("stencil", stencilNode);

        ObjectNode stencilsetNode = objectMapper.createObjectNode();
        stencilsetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        stencilsetNode.put("url", "../editor/stencilsets/bpmn2.0/bpmn2.0.json");
        modelNode.put("stencilset", stencilsetNode);

        ArrayNode shapesArrayNode = objectMapper.createArrayNode();

        Process mainProcess = model.getMainProcess();

        ObjectNode propertiesNode = objectMapper.createObjectNode();

        if (StringUtils.isNotEmpty(mainProcess.getId())) {
            propertiesNode.put(PROPERTY_PROCESS_ID, mainProcess.getId());
        }

        if (StringUtils.isNotEmpty(mainProcess.getName())) {
            propertiesNode.put(PROPERTY_NAME, mainProcess.getName());
        }

        if (mainProcess.isExecutable() == false) {
            propertiesNode.put(PROPERTY_PROCESS_EXECUTABLE, PROPERTY_VALUE_NO);
        }

        propertiesNode.put(PROPERTY_PROCESS_NAMESPACE,
                model.getTargetNamespace());

        convertListenersToJson(mainProcess.getEventListeners(), propertiesNode);

        if (StringUtils.isNotEmpty(mainProcess.getDocumentation())) {
            propertiesNode.put(PROPERTY_DOCUMENTATION,
                    mainProcess.getDocumentation());
        }

        if (!mainProcess.getDataObjects().isEmpty()) {
            convertDataPropertiesToJson(mainProcess.getDataObjects(),
                    propertiesNode);
        }

        modelNode.put(EDITOR_SHAPE_PROPERTIES, propertiesNode);

        if (!model.getPools().isEmpty()) {
            for (Pool pool : model.getPools()) {
                GraphicInfo poolGraphicInfo = model
                        .getGraphicInfo(pool.getId());
                ObjectNode poolNode = BpmnJsonConverterUtil.createChildShape(
                        pool.getId(), STENCIL_POOL, poolGraphicInfo.getX()
                                + poolGraphicInfo.getWidth(),
                        poolGraphicInfo.getY() + poolGraphicInfo.getHeight(),
                        poolGraphicInfo.getX(), poolGraphicInfo.getY());
                shapesArrayNode.add(poolNode);

                ObjectNode poolPropertiesNode = objectMapper.createObjectNode();
                poolPropertiesNode.put(PROPERTY_OVERRIDE_ID, pool.getId());
                poolPropertiesNode.put(PROPERTY_PROCESS_ID,
                        pool.getProcessRef());

                if (pool.isExecutable() == false) {
                    poolPropertiesNode.put(PROPERTY_PROCESS_EXECUTABLE,
                            PROPERTY_VALUE_NO);
                }

                if (StringUtils.isNotEmpty(pool.getName())) {
                    poolPropertiesNode.put(PROPERTY_NAME, pool.getName());
                }

                poolNode.put(EDITOR_SHAPE_PROPERTIES, poolPropertiesNode);
                poolNode.put(EDITOR_OUTGOING, objectMapper.createArrayNode());

                ArrayNode laneShapesArrayNode = objectMapper.createArrayNode();
                poolNode.put(EDITOR_CHILD_SHAPES, laneShapesArrayNode);

                Process process = model.getProcess(pool.getId());

                if (process != null) {
                    processFlowElements(
                            process.findFlowElementsOfType(SequenceFlow.class),
                            model, shapesArrayNode, poolGraphicInfo.getX(),
                            poolGraphicInfo.getY());

                    for (Lane lane : process.getLanes()) {
                        GraphicInfo laneGraphicInfo = model.getGraphicInfo(lane
                                .getId());
                        ObjectNode laneNode = BpmnJsonConverterUtil
                                .createChildShape(lane.getId(), STENCIL_LANE,
                                        laneGraphicInfo.getX()
                                                + laneGraphicInfo.getWidth(),
                                        laneGraphicInfo.getY()
                                                + laneGraphicInfo.getHeight(),
                                        laneGraphicInfo.getX(),
                                        laneGraphicInfo.getY());
                        laneShapesArrayNode.add(laneNode);

                        ObjectNode lanePropertiesNode = objectMapper
                                .createObjectNode();
                        lanePropertiesNode.put(PROPERTY_OVERRIDE_ID,
                                lane.getId());

                        if (StringUtils.isNotEmpty(lane.getName())) {
                            lanePropertiesNode.put(PROPERTY_NAME,
                                    lane.getName());
                        }

                        laneNode.put(EDITOR_SHAPE_PROPERTIES,
                                lanePropertiesNode);

                        ArrayNode elementShapesArrayNode = objectMapper
                                .createArrayNode();
                        laneNode.put(EDITOR_CHILD_SHAPES,
                                elementShapesArrayNode);
                        laneNode.put(EDITOR_OUTGOING,
                                objectMapper.createArrayNode());

                        for (FlowElement flowElement : process
                                .getFlowElements()) {
                            if (lane.getFlowReferences().contains(
                                    flowElement.getId())) {
                                Class<? extends BaseBpmnJsonConverter> converter = convertersToJsonMap
                                        .get(flowElement.getClass());

                                if (converter != null) {
                                    try {
                                        converter.newInstance().convertToJson(
                                                flowElement, this, model,
                                                elementShapesArrayNode,
                                                laneGraphicInfo.getX(),
                                                laneGraphicInfo.getY());
                                    } catch (Exception e) {
                                        LOGGER.error("Error converting {}",
                                                flowElement, e);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } else {
            processFlowElements(model.getMainProcess().getFlowElements(),
                    model, shapesArrayNode, 0.0, 0.0);
        }

        modelNode.put(EDITOR_CHILD_SHAPES, shapesArrayNode);

        return modelNode;
    }

    @Override
    public void processFlowElements(
            Collection<? extends FlowElement> flowElements, BpmnModel model,
            ArrayNode shapesArrayNode, double subProcessX, double subProcessY) {
        for (FlowElement flowElement : flowElements) {
            Class<? extends BaseBpmnJsonConverter> converter = convertersToJsonMap
                    .get(flowElement.getClass());

            if (converter != null) {
                try {
                    converter.newInstance().convertToJson(flowElement, this,
                            model, shapesArrayNode, subProcessX, subProcessY);
                } catch (Exception e) {
                    LOGGER.error("Error converting {}", flowElement, e);
                }
            }
        }
    }

    public BpmnModel convertToBpmnModel(JsonNode modelNode) {
        BpmnModel bpmnModel = new BpmnModel();
        Map<String, JsonNode> shapeMap = new HashMap<String, JsonNode>();
        Map<String, JsonNode> sourceRefMap = new HashMap<String, JsonNode>();
        Map<String, JsonNode> edgeMap = new HashMap<String, JsonNode>();
        Map<String, List<JsonNode>> sourceAndTargetMap = new HashMap<String, List<JsonNode>>();
        readShapeDI(modelNode, 0, 0, shapeMap, sourceRefMap, bpmnModel);
        filterAllEdges(modelNode, edgeMap, sourceAndTargetMap, shapeMap,
                sourceRefMap);
        readEdgeDI(edgeMap, sourceAndTargetMap, bpmnModel);

        ArrayNode shapesArrayNode = (ArrayNode) modelNode
                .get(EDITOR_CHILD_SHAPES);

        boolean emptyPoolFound = true;

        // first create the pool structure
        for (JsonNode shapeNode : shapesArrayNode) {
            String stencilId = BpmnJsonConverterUtil.getStencilId(shapeNode);

            if (STENCIL_POOL.equals(stencilId)) {
                Pool pool = new Pool();
                pool.setId(BpmnJsonConverterUtil.getElementId(shapeNode));
                pool.setName(JsonConverterUtil.getPropertyValueAsString(
                        PROPERTY_NAME, shapeNode));
                pool.setProcessRef(JsonConverterUtil.getPropertyValueAsString(
                        PROPERTY_PROCESS_ID, shapeNode));
                pool.setExecutable(JsonConverterUtil.getPropertyValueAsBoolean(
                        PROPERTY_PROCESS_EXECUTABLE, shapeNode, true));
                bpmnModel.getPools().add(pool);

                Process process = new Process();
                process.setId(pool.getProcessRef());
                process.setName(pool.getName());
                process.setExecutable(pool.isExecutable());
                bpmnModel.addProcess(process);

                processJsonElements(shapesArrayNode, modelNode, process,
                        shapeMap);

                ArrayNode laneArrayNode = (ArrayNode) shapeNode
                        .get(EDITOR_CHILD_SHAPES);

                for (JsonNode laneNode : laneArrayNode) {
                    // should be a lane, but just check to be certain
                    String laneStencilId = BpmnJsonConverterUtil
                            .getStencilId(laneNode);

                    if (STENCIL_LANE.equals(laneStencilId)) {
                        emptyPoolFound = false;

                        Lane lane = new Lane();
                        lane.setId(BpmnJsonConverterUtil.getElementId(laneNode));
                        lane.setName(JsonConverterUtil
                                .getPropertyValueAsString(PROPERTY_NAME,
                                        laneNode));
                        lane.setParentProcess(process);
                        process.getLanes().add(lane);

                        processJsonElements(laneNode.get(EDITOR_CHILD_SHAPES),
                                modelNode, lane, shapeMap);
                    }
                }
            }
        }

        if (emptyPoolFound) {
            JsonNode processIdNode = JsonConverterUtil.getProperty(
                    PROPERTY_PROCESS_ID, modelNode);
            Process process = new Process();
            bpmnModel.getProcesses().add(process);

            if ((processIdNode != null)
                    && StringUtils.isNotEmpty(processIdNode.asText())) {
                process.setId(processIdNode.asText());
            }

            JsonNode processNameNode = JsonConverterUtil.getProperty(
                    PROPERTY_NAME, modelNode);

            if ((processNameNode != null)
                    && StringUtils.isNotEmpty(processNameNode.asText())) {
                process.setName(processNameNode.asText());
            }

            JsonNode processExecutableNode = JsonConverterUtil.getProperty(
                    PROPERTY_PROCESS_EXECUTABLE, modelNode);

            if ((processExecutableNode != null)
                    && StringUtils.isNotEmpty(processExecutableNode.asText())) {
                process.setExecutable(JsonConverterUtil
                        .getPropertyValueAsBoolean(PROPERTY_PROCESS_EXECUTABLE,
                                modelNode));
            }

            JsonNode processTargetNamespace = JsonConverterUtil.getProperty(
                    PROPERTY_PROCESS_NAMESPACE, modelNode);

            if ((processTargetNamespace != null)
                    && StringUtils.isNotEmpty(processTargetNamespace.asText())) {
                bpmnModel.setTargetNamespace(processTargetNamespace.asText());
            }

            JsonNode processExecutionListenerNode = modelNode.get(
                    EDITOR_SHAPE_PROPERTIES).get(PROPERTY_EXECUTION_LISTENERS);

            if ((processExecutionListenerNode != null)
                    && StringUtils.isNotEmpty(processExecutionListenerNode
                            .asText())) {
                process.setExecutionListeners(convertJsonToListeners(processExecutionListenerNode));
            }

            JsonNode processEventListenerNode = modelNode.get(
                    EDITOR_SHAPE_PROPERTIES).get(PROPERTY_EVENT_LISTENERS);

            if (processEventListenerNode != null) {
                process.setEventListeners(convertJsonToEventListeners(processEventListenerNode));
            }

            JsonNode processDataPropertiesNode = modelNode.get(
                    EDITOR_SHAPE_PROPERTIES).get(PROPERTY_DATA_PROPERTIES);

            if (processDataPropertiesNode != null) {
                List<ValuedDataObject> dataObjects = convertJsonToDataProperties(
                        processDataPropertiesNode, process);
                process.setDataObjects(dataObjects);
                process.getFlowElements().addAll(dataObjects);
            }

            processJsonElements(shapesArrayNode, modelNode, process, shapeMap);
        }

        // sequence flows are now all on root level
        Map<String, SubProcess> subShapesMap = new HashMap<String, SubProcess>();

        for (Process process : bpmnModel.getProcesses()) {
            for (FlowElement flowElement : process
                    .findFlowElementsOfType(SubProcess.class)) {
                SubProcess subProcess = (SubProcess) flowElement;
                fillSubShapes(subShapesMap, subProcess);
            }

            if (!subShapesMap.isEmpty()) {
                List<String> removeSubFlowsList = new ArrayList<String>();
                List<SequenceFlow> sequenceFlowList = process
                        .findFlowElementsOfType(SequenceFlow.class);

                for (FlowElement flowElement : sequenceFlowList) {
                    SequenceFlow sequenceFlow = (SequenceFlow) flowElement;

                    if ((process.getFlowElement(flowElement.getId()) != null)
                            && subShapesMap.containsKey(sequenceFlow
                                    .getSourceRef())) {
                        SubProcess subProcess = subShapesMap.get(sequenceFlow
                                .getSourceRef());
                        subProcess.addFlowElement(sequenceFlow);
                        removeSubFlowsList.add(sequenceFlow.getId());
                    }
                }

                for (String flowId : removeSubFlowsList) {
                    process.removeFlowElement(flowId);
                }
            }
        }

        // boundary events only contain attached ref id
        for (Process process : bpmnModel.getProcesses()) {
            postProcessElements(process, process.getFlowElements());
        }

        return bpmnModel;
    }

    @Override
    public void processJsonElements(JsonNode shapesArrayNode,
            JsonNode modelNode, BaseElement parentElement,
            Map<String, JsonNode> shapeMap) {
        for (JsonNode shapeNode : shapesArrayNode) {
            Class<? extends BaseBpmnJsonConverter> converter = convertersToBpmnMap
                    .get(BpmnJsonConverterUtil.getStencilId(shapeNode));

            if (converter != null) {
                try {
                    converter.newInstance().convertToBpmnModel(shapeNode,
                            modelNode, this, parentElement, shapeMap);
                } catch (Exception e) {
                    LOGGER.error("Error converting {}",
                            BpmnJsonConverterUtil.getStencilId(shapeNode), e);
                }
            }
        }
    }

    private List<ActivitiListener> convertJsonToListeners(JsonNode listenersNode) {
        List<ActivitiListener> executionListeners = new ArrayList<ActivitiListener>();

        try {
            listenersNode = objectMapper.readTree(listenersNode.asText());
        } catch (Exception e) {
            LOGGER.info("Listeners node can not be read", e);
        }

        JsonNode itemsArrayNode = listenersNode
                .get(EDITOR_PROPERTIES_GENERAL_ITEMS);

        if (itemsArrayNode != null) {
            for (JsonNode itemNode : itemsArrayNode) {
                JsonNode typeNode = itemNode
                        .get(PROPERTY_EXECUTION_LISTENER_EVENT);

                if ((typeNode != null)
                        && StringUtils.isNotEmpty(typeNode.asText())) {
                    ActivitiListener listener = new ActivitiListener();
                    listener.setEvent(typeNode.asText());

                    if (StringUtils.isNotEmpty(itemNode.get(
                            PROPERTY_EXECUTION_LISTENER_CLASS).asText())) {
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EXECUTION_LISTENER_CLASS).asText());
                    } else if (StringUtils.isNotEmpty(itemNode.get(
                            PROPERTY_EXECUTION_LISTENER_EXPRESSION).asText())) {
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_EXPRESSION);
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EXECUTION_LISTENER_EXPRESSION)
                                .asText());
                    } else if (StringUtils.isNotEmpty(itemNode.get(
                            PROPERTY_EXECUTION_LISTENER_DELEGATEEXPRESSION)
                            .asText())) {
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EXECUTION_LISTENER_DELEGATEEXPRESSION)
                                .asText());
                    }

                    // resolve the listener feild
                    JsonNode listenerFieldsNode = null;
                    JsonNode listenerFieldsArrayNode = null;
                    listenerFieldsNode = itemNode
                            .get(PROPERTY_EXECUTION_LISTENER_FIELDS);

                    if ((listenerFieldsNode != null)
                            && StringUtils.isNotEmpty(listenerFieldsNode
                                    .asText())
                            && !("undefined"
                                    .equals(listenerFieldsNode.asText()))) {
                        if (listenerFieldsNode.isValueNode()) {
                            try {
                                listenerFieldsNode = objectMapper
                                        .readTree(listenerFieldsNode.asText());
                            } catch (Exception e) {
                                LOGGER.info(
                                        "Listener fields node can not be read",
                                        e);
                            }
                        }
                    }

                    if (listenerFieldsNode != null) {
                        listenerFieldsArrayNode = listenerFieldsNode
                                .get(EDITOR_PROPERTIES_GENERAL_ITEMS);

                        List<FieldExtension> fields = new ArrayList<FieldExtension>();

                        if (listenerFieldsArrayNode != null) {
                            for (JsonNode fieldNode : listenerFieldsArrayNode) {
                                JsonNode fieldNameNode = fieldNode
                                        .get(PROPERTY_EXECUTION_LISTENER_FIELD_NAME);

                                if ((fieldNameNode != null)
                                        && StringUtils.isNotEmpty(fieldNameNode
                                                .asText())) {
                                    FieldExtension field = new FieldExtension();
                                    field.setFieldName(fieldNameNode.asText());
                                    field.setStringValue(getValueAsString(
                                            PROPERTY_EXECUTION_LISTENER_FIELD_VALUE,
                                            fieldNode));
                                    field.setExpression(getValueAsString(
                                            PROPERTY_EXECUTION_LISTENER_EXPRESSION,
                                            fieldNode));
                                    fields.add(field);
                                }
                            }
                        }

                        listener.setFieldExtensions(fields);
                    }

                    executionListeners.add(listener);
                }
            }
        }

        return executionListeners;
    }

    private String getValueAsString(String name, JsonNode objectNode) {
        String propertyValue = null;
        JsonNode propertyNode = objectNode.get(name);

        if ((propertyNode != null)
                && ("null".equalsIgnoreCase(propertyNode.asText()) == false)) {
            propertyValue = propertyNode.asText();
        }

        return propertyValue;
    }

    private List<EventListener> convertJsonToEventListeners(
            JsonNode listenersNode) {
        List<EventListener> eventListeners = new ArrayList<EventListener>();

        if (StringUtils.isEmpty(listenersNode.asText())) {
            return eventListeners;
        }

        try {
            listenersNode = objectMapper.readTree(listenersNode.asText());
        } catch (Exception e) {
            LOGGER.info("Event listeners node can not be read", e);
        }

        JsonNode itemsArrayNode = listenersNode
                .get(EDITOR_PROPERTIES_GENERAL_ITEMS);

        if (itemsArrayNode != null) {
            for (JsonNode itemNode : itemsArrayNode) {
                EventListener listener = new EventListener();

                if (isNotEmpty(PROPERTY_EVENT_LISTENER_EVENTS, itemNode)) {
                    listener.setEvents(itemNode.get(
                            PROPERTY_EVENT_LISTENER_EVENTS).asText());
                }

                if (isNotEmpty(PROPERTY_EVENT_LISTENER_ENTITY_TYPE, itemNode)) {
                    listener.setEntityType(itemNode.get(
                            PROPERTY_EVENT_LISTENER_ENTITY_TYPE).asText());
                }

                if (isNotEmpty(PROPERTY_EVENT_LISTENER_CLASS, itemNode)) {
                    listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
                    listener.setImplementation(itemNode.get(
                            PROPERTY_EVENT_LISTENER_CLASS).asText());
                } else if (isNotEmpty(
                        PROPERTY_EVENT_LISTENER_DELEGATEEXPRESSION, itemNode)) {
                    listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
                    listener.setImplementation(itemNode.get(
                            PROPERTY_EVENT_LISTENER_DELEGATEEXPRESSION)
                            .asText());
                } else if (isNotEmpty(PROPERTY_EVENT_LISTENER_THROW_EVENT,
                        itemNode)) {
                    String throwEventType = itemNode.get(
                            PROPERTY_EVENT_LISTENER_THROW_EVENT).asText();

                    if (PROPERTY_EVENT_LISTENER_THROW_SIGNAL
                            .equals(throwEventType)) {
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EVENT_LISTENER_THROW_REFERENCE)
                                .asText());
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT);
                    } else if (PROPERTY_EVENT_LISTENER_THROW_GLOBAL_SIGNAL
                            .equals(throwEventType)) {
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EVENT_LISTENER_THROW_REFERENCE)
                                .asText());
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT);
                    } else if (PROPERTY_EVENT_LISTENER_THROW_MESSAGE
                            .equals(throwEventType)) {
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EVENT_LISTENER_THROW_REFERENCE)
                                .asText());
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT);
                    } else if (PROPERTY_EVENT_LISTENER_THROW_ERROR
                            .equals(throwEventType)) {
                        listener.setImplementation(itemNode.get(
                                PROPERTY_EVENT_LISTENER_THROW_REFERENCE)
                                .asText());
                        listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT);
                    } else {
                        // Not a valid throw event type, ignore this listener definition
                        listener = null;
                    }
                } else {
                    // No properties have been provided to have a valid implementation
                    listener = null;
                }

                if (listener != null) {
                    eventListeners.add(listener);
                }
            }
        }

        return eventListeners;
    }

    protected List<ValuedDataObject> convertJsonToDataProperties(
            JsonNode objectNode, BaseElement element) {
        List<ValuedDataObject> dataObjects = new ArrayList<ValuedDataObject>();

        if (objectNode != null) {
            if (objectNode.isValueNode()
                    && StringUtils.isNotEmpty(objectNode.asText())) {
                try {
                    objectNode = objectMapper.readTree(objectNode.asText());
                } catch (Exception e) {
                    LOGGER.info("Data properties node cannot be read", e);
                }
            }

            JsonNode itemsArrayNode = objectNode
                    .get(EDITOR_PROPERTIES_GENERAL_ITEMS);

            if (itemsArrayNode != null) {
                for (JsonNode dataNode : itemsArrayNode) {
                    JsonNode dataIdNode = dataNode.get(PROPERTY_DATA_ID);

                    if ((dataIdNode != null)
                            && StringUtils.isNotEmpty(dataIdNode.asText())) {
                        ValuedDataObject dataObject = null;
                        ItemDefinition itemSubjectRef = new ItemDefinition();
                        String dataType = dataNode.get(PROPERTY_DATA_TYPE)
                                .asText();

                        if (dataType.equals("string")) {
                            dataObject = new StringDataObject();
                        } else if (dataType.equals("int")) {
                            dataObject = new IntegerDataObject();
                        } else if (dataType.equals("long")) {
                            dataObject = new LongDataObject();
                        } else if (dataType.equals("double")) {
                            dataObject = new DoubleDataObject();
                        } else if (dataType.equals("boolean")) {
                            dataObject = new BooleanDataObject();
                        } else if (dataType.equals("datetime")) {
                            dataObject = new DateDataObject();
                        } else {
                            LOGGER.error("Error converting {}",
                                    dataIdNode.asText());
                        }

                        if (null != dataObject) {
                            dataObject.setId(dataIdNode.asText());
                            dataObject.setName(dataNode.get(PROPERTY_DATA_NAME)
                                    .asText());

                            itemSubjectRef.setStructureRef("xsd:" + dataType);
                            dataObject.setItemSubjectRef(itemSubjectRef);

                            if (dataObject instanceof DateDataObject) {
                                try {
                                    dataObject.setValue(sdf.parse(dataNode.get(
                                            PROPERTY_DATA_VALUE).asText()));
                                } catch (Exception e) {
                                    LOGGER.error("Error converting {}",
                                            dataObject.getName(), e);
                                }
                            } else {
                                dataObject.setValue(dataNode.get(
                                        PROPERTY_DATA_VALUE).asText());
                            }

                            dataObjects.add(dataObject);
                        }
                    }
                }
            }
        }

        return dataObjects;
    }

    protected void convertDataPropertiesToJson(
            List<ValuedDataObject> dataObjects, ObjectNode propertiesNode) {
        ObjectNode dataPropertiesNode = objectMapper.createObjectNode();
        ArrayNode itemsNode = objectMapper.createArrayNode();

        for (ValuedDataObject dObj : dataObjects) {
            ObjectNode propertyItemNode = objectMapper.createObjectNode();
            propertyItemNode.put(PROPERTY_DATA_ID, dObj.getId());
            propertyItemNode.put(PROPERTY_DATA_NAME, dObj.getName());

            String itemSubjectRefQName = dObj.getItemSubjectRef()
                    .getStructureRef();

            // remove namespace prefix
            String dataType = itemSubjectRefQName.substring(itemSubjectRefQName
                    .indexOf(':') + 1);
            propertyItemNode.put(PROPERTY_DATA_TYPE, dataType);

            Object dObjValue = dObj.getValue();
            String value = null;

            if (null == dObjValue) {
                propertyItemNode.put(PROPERTY_DATA_VALUE, "");
            } else {
                if ("datetime".equals(dataType)) {
                    value = sdf.format(dObjValue);
                } else {
                    value = dObjValue.toString();
                }

                propertyItemNode.put(PROPERTY_DATA_VALUE, value.toString());
            }

            itemsNode.add(propertyItemNode);
        }

        dataPropertiesNode.put("totalCount", itemsNode.size());
        dataPropertiesNode.put(EDITOR_PROPERTIES_GENERAL_ITEMS, itemsNode);
        propertiesNode.put("dataproperties", dataPropertiesNode);
    }

    private boolean isNotEmpty(String propertyName, JsonNode node) {
        JsonNode value = node.get(propertyName);

        if (value != null) {
            return StringUtils.isNotEmpty(value.asText());
        }

        return false;
    }

    private void fillSubShapes(Map<String, SubProcess> subShapesMap,
            SubProcess subProcess) {
        for (FlowElement flowElement : subProcess.getFlowElements()) {
            if (flowElement instanceof SubProcess) {
                SubProcess childSubProcess = (SubProcess) flowElement;
                fillSubShapes(subShapesMap, childSubProcess);
            } else {
                subShapesMap.put(flowElement.getId(), subProcess);
            }
        }
    }

    private void postProcessElements(FlowElementsContainer process,
            Collection<FlowElement> flowElementList) {
        for (FlowElement flowElement : flowElementList) {
            if (flowElement instanceof BoundaryEvent) {
                BoundaryEvent boundaryEvent = (BoundaryEvent) flowElement;
                Activity activity = retrieveAttachedRefObject(
                        boundaryEvent.getAttachedToRefId(),
                        process.getFlowElements());

                if (activity == null) {
                    LOGGER.warn("Boundary event " + boundaryEvent.getId()
                            + " is not attached to any activity");
                } else {
                    boundaryEvent.setAttachedToRef(activity);
                    activity.getBoundaryEvents().add(boundaryEvent);
                }
            } else if (flowElement instanceof SubProcess) {
                SubProcess subProcess = (SubProcess) flowElement;
                postProcessElements(subProcess, subProcess.getFlowElements());
            } else if (flowElement instanceof SequenceFlow) {
                SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                FlowElement sourceFlowElement = process
                        .getFlowElement(sequenceFlow.getSourceRef());

                if (sourceFlowElement instanceof FlowNode) {
                    ((FlowNode) sourceFlowElement).getOutgoingFlows().add(
                            sequenceFlow);
                }

                FlowElement targerFlowElement = process
                        .getFlowElement(sequenceFlow.getTargetRef());

                if (targerFlowElement instanceof FlowNode) {
                    ((FlowNode) targerFlowElement).getIncomingFlows().add(
                            sequenceFlow);
                }
            }
        }
    }

    private Activity retrieveAttachedRefObject(String attachedToRefId,
            Collection<FlowElement> flowElementList) {
        for (FlowElement flowElement : flowElementList) {
            if (attachedToRefId.equals(flowElement.getId())) {
                return (Activity) flowElement;
            } else if (flowElement instanceof SubProcess) {
                SubProcess subProcess = (SubProcess) flowElement;
                Activity activity = retrieveAttachedRefObject(attachedToRefId,
                        subProcess.getFlowElements());

                if (activity != null) {
                    return activity;
                }
            }
        }

        return null;
    }

    private void readShapeDI(JsonNode objectNode, double parentX,
            double parentY, Map<String, JsonNode> shapeMap,
            Map<String, JsonNode> sourceRefMap, BpmnModel bpmnModel) {
        if (objectNode.get(EDITOR_CHILD_SHAPES) != null) {
            for (JsonNode jsonChildNode : objectNode.get(EDITOR_CHILD_SHAPES)) {
                String stencilId = BpmnJsonConverterUtil
                        .getStencilId(jsonChildNode);

                if (STENCIL_SEQUENCE_FLOW.equals(stencilId) == false) {
                    GraphicInfo graphicInfo = new GraphicInfo();

                    JsonNode boundsNode = jsonChildNode.get(EDITOR_BOUNDS);
                    ObjectNode upperLeftNode = (ObjectNode) boundsNode
                            .get(EDITOR_BOUNDS_UPPER_LEFT);
                    graphicInfo.setX(upperLeftNode.get(EDITOR_BOUNDS_X)
                            .asDouble() + parentX);
                    graphicInfo.setY(upperLeftNode.get(EDITOR_BOUNDS_Y)
                            .asDouble() + parentY);

                    ObjectNode lowerRightNode = (ObjectNode) boundsNode
                            .get(EDITOR_BOUNDS_LOWER_RIGHT);
                    graphicInfo.setWidth(lowerRightNode.get(EDITOR_BOUNDS_X)
                            .asDouble() - graphicInfo.getX() + parentX);
                    graphicInfo.setHeight(lowerRightNode.get(EDITOR_BOUNDS_Y)
                            .asDouble() - graphicInfo.getY() + parentY);

                    String childShapeId = jsonChildNode.get(EDITOR_SHAPE_ID)
                            .asText();
                    bpmnModel.addGraphicInfo(
                            BpmnJsonConverterUtil.getElementId(jsonChildNode),
                            graphicInfo);

                    shapeMap.put(childShapeId, jsonChildNode);

                    ArrayNode outgoingNode = (ArrayNode) jsonChildNode
                            .get("outgoing");

                    if ((outgoingNode != null) && (outgoingNode.size() > 0)) {
                        for (JsonNode outgoingChildNode : outgoingNode) {
                            JsonNode resourceNode = outgoingChildNode
                                    .get(EDITOR_SHAPE_ID);

                            if (resourceNode != null) {
                                sourceRefMap.put(resourceNode.asText(),
                                        jsonChildNode);
                            }
                        }
                    }

                    readShapeDI(jsonChildNode, graphicInfo.getX(),
                            graphicInfo.getY(), shapeMap, sourceRefMap,
                            bpmnModel);
                }
            }
        }
    }

    private void filterAllEdges(JsonNode objectNode,
            Map<String, JsonNode> edgeMap,
            Map<String, List<JsonNode>> sourceAndTargetMap,
            Map<String, JsonNode> shapeMap, Map<String, JsonNode> sourceRefMap) {
        if (objectNode.get(EDITOR_CHILD_SHAPES) != null) {
            for (JsonNode jsonChildNode : objectNode.get(EDITOR_CHILD_SHAPES)) {
                ObjectNode childNode = (ObjectNode) jsonChildNode;
                String stencilId = BpmnJsonConverterUtil
                        .getStencilId(childNode);

                if (STENCIL_SUB_PROCESS.equals(stencilId)) {
                    filterAllEdges(childNode, edgeMap, sourceAndTargetMap,
                            shapeMap, sourceRefMap);
                } else if (STENCIL_SEQUENCE_FLOW.equals(stencilId)) {
                    String childEdgeId = BpmnJsonConverterUtil
                            .getElementId(childNode);
                    String targetRefId = childNode.get("target")
                            .get(EDITOR_SHAPE_ID).asText();
                    List<JsonNode> sourceAndTargetList = new ArrayList<JsonNode>();
                    sourceAndTargetList.add(sourceRefMap.get(childNode.get(
                            EDITOR_SHAPE_ID).asText()));
                    sourceAndTargetList.add(shapeMap.get(targetRefId));

                    edgeMap.put(childEdgeId, childNode);
                    sourceAndTargetMap.put(childEdgeId, sourceAndTargetList);
                }
            }
        }
    }

    public void convertListenersToJson(List<EventListener> eventListeners,
            ObjectNode propertiesNode) {
        ObjectNode listenersNode = objectMapper.createObjectNode();
        ArrayNode itemsNode = objectMapper.createArrayNode();

        for (EventListener listener : eventListeners) {
            ObjectNode propertyItemNode = objectMapper.createObjectNode();

            propertyItemNode.put(PROPERTY_EVENT_LISTENER_EVENTS,
                    listener.getEvents());
            propertyItemNode.put(PROPERTY_EVENT_LISTENER_ENTITY_TYPE,
                    listener.getEntityType());

            if (ImplementationType.IMPLEMENTATION_TYPE_CLASS.equals(listener
                    .getImplementationType())) {
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_CLASS,
                        listener.getImplementation());
            } else if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION
                    .equals(listener.getImplementationType())) {
                propertyItemNode.put(
                        PROPERTY_EVENT_LISTENER_DELEGATEEXPRESSION,
                        listener.getImplementation());
            } else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_SIGNAL_EVENT
                    .equals(listener.getImplementationType())) {
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_EVENT,
                        PROPERTY_EVENT_LISTENER_THROW_SIGNAL);
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_REFERENCE,
                        listener.getImplementation());
            } else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_GLOBAL_SIGNAL_EVENT
                    .equals(listener.getImplementationType())) {
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_EVENT,
                        PROPERTY_EVENT_LISTENER_THROW_GLOBAL_SIGNAL);
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_REFERENCE,
                        listener.getImplementation());
            } else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_MESSAGE_EVENT
                    .equals(listener.getImplementationType())) {
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_EVENT,
                        PROPERTY_EVENT_LISTENER_THROW_MESSAGE);
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_REFERENCE,
                        listener.getImplementation());
            } else if (ImplementationType.IMPLEMENTATION_TYPE_THROW_ERROR_EVENT
                    .equals(listener.getImplementationType())) {
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_EVENT,
                        PROPERTY_EVENT_LISTENER_THROW_ERROR);
                propertyItemNode.put(PROPERTY_EVENT_LISTENER_THROW_REFERENCE,
                        listener.getImplementation());
            }

            itemsNode.add(propertyItemNode);
        }

        listenersNode.put("totalCount", itemsNode.size());
        listenersNode.put(EDITOR_PROPERTIES_GENERAL_ITEMS, itemsNode);
        propertiesNode.put(PROPERTY_EVENT_LISTENERS, listenersNode);
    }

    private void readEdgeDI(Map<String, JsonNode> edgeMap,
            Map<String, List<JsonNode>> sourceAndTargetMap, BpmnModel bpmnModel) {
        for (String edgeId : edgeMap.keySet()) {
            JsonNode edgeNode = edgeMap.get(edgeId);
            List<JsonNode> sourceAndTargetList = sourceAndTargetMap.get(edgeId);

            JsonNode sourceRefNode = sourceAndTargetList.get(0);
            JsonNode targetRefNode = sourceAndTargetList.get(1);

            if (sourceRefNode == null) {
                LOGGER.info("Skipping edge {} because source ref is null",
                        edgeId);

                continue;
            }

            if (targetRefNode == null) {
                LOGGER.info("Skipping edge {} because target ref is null",
                        edgeId);

                continue;
            }

            JsonNode dockersNode = edgeNode.get(EDITOR_DOCKERS);
            double sourceDockersX = dockersNode.get(0).get(EDITOR_BOUNDS_X)
                    .doubleValue();
            double sourceDockersY = dockersNode.get(0).get(EDITOR_BOUNDS_Y)
                    .doubleValue();

            GraphicInfo sourceInfo = bpmnModel
                    .getGraphicInfo(BpmnJsonConverterUtil
                            .getElementId(sourceRefNode));
            GraphicInfo targetInfo = bpmnModel
                    .getGraphicInfo(BpmnJsonConverterUtil
                            .getElementId(targetRefNode));

            /*
             * JsonNode sourceRefBoundsNode = sourceRefNode.get(EDITOR_BOUNDS); BoundsLocation
             * sourceRefUpperLeftLocation = getLocation(EDITOR_BOUNDS_UPPER_LEFT, sourceRefBoundsNode); BoundsLocation
             * sourceRefLowerRightLocation = getLocation(EDITOR_BOUNDS_LOWER_RIGHT, sourceRefBoundsNode);
             * 
             * JsonNode targetRefBoundsNode = targetRefNode.get(EDITOR_BOUNDS); BoundsLocation
             * targetRefUpperLeftLocation = getLocation(EDITOR_BOUNDS_UPPER_LEFT, targetRefBoundsNode); BoundsLocation
             * targetRefLowerRightLocation = getLocation(EDITOR_BOUNDS_LOWER_RIGHT, targetRefBoundsNode);
             */
            double sourceRefLineX = sourceInfo.getX() + sourceDockersX;
            double sourceRefLineY = sourceInfo.getY() + sourceDockersY;

            double nextPointInLineX;
            double nextPointInLineY;

            nextPointInLineX = dockersNode.get(1).get(EDITOR_BOUNDS_X)
                    .doubleValue();
            nextPointInLineY = dockersNode.get(1).get(EDITOR_BOUNDS_Y)
                    .doubleValue();

            if (dockersNode.size() == 2) {
                nextPointInLineX += targetInfo.getX();
                nextPointInLineY += targetInfo.getY();
            }

            Line2D firstLine = new Line2D(sourceRefLineX, sourceRefLineY,
                    nextPointInLineX, nextPointInLineY);

            String sourceRefStencilId = BpmnJsonConverterUtil
                    .getStencilId(sourceRefNode);
            String targetRefStencilId = BpmnJsonConverterUtil
                    .getStencilId(targetRefNode);

            List<GraphicInfo> graphicInfoList = new ArrayList<GraphicInfo>();

            if (DI_CIRCLES.contains(sourceRefStencilId)) {
                Circle2D eventCircle = new Circle2D(sourceInfo.getX()
                        + sourceDockersX, sourceInfo.getY() + sourceDockersY,
                        sourceDockersX);

                Collection<Point2D> intersections = eventCircle
                        .intersections(firstLine);
                Point2D intersection = intersections.iterator().next();
                graphicInfoList.add(createGraphicInfo(intersection.x(),
                        intersection.y()));
            } else if (DI_RECTANGLES.contains(sourceRefStencilId)) {
                Polyline2D rectangle = createRectangle(sourceInfo);

                Collection<Point2D> intersections = rectangle
                        .intersections(firstLine);
                Point2D intersection = intersections.iterator().next();
                graphicInfoList.add(createGraphicInfo(intersection.x(),
                        intersection.y()));
            } else if (DI_GATEWAY.contains(sourceRefStencilId)) {
                Polyline2D gatewayRectangle = createGateway(sourceInfo);

                Collection<Point2D> intersections = gatewayRectangle
                        .intersections(firstLine);
                Point2D intersection = intersections.iterator().next();
                graphicInfoList.add(createGraphicInfo(intersection.x(),
                        intersection.y()));
            }

            Line2D lastLine = null;

            if (dockersNode.size() > 2) {
                for (int i = 1; i < (dockersNode.size() - 1); i++) {
                    double x = dockersNode.get(i).get(EDITOR_BOUNDS_X)
                            .doubleValue();
                    double y = dockersNode.get(i).get(EDITOR_BOUNDS_Y)
                            .doubleValue();
                    graphicInfoList.add(createGraphicInfo(x, y));
                }

                double startLastLineX = dockersNode.get(dockersNode.size() - 2)
                        .get(EDITOR_BOUNDS_X).doubleValue();
                double startLastLineY = dockersNode.get(dockersNode.size() - 2)
                        .get(EDITOR_BOUNDS_Y).doubleValue();

                double endLastLineX = dockersNode.get(dockersNode.size() - 1)
                        .get(EDITOR_BOUNDS_X).doubleValue();
                double endLastLineY = dockersNode.get(dockersNode.size() - 1)
                        .get(EDITOR_BOUNDS_Y).doubleValue();

                endLastLineX += targetInfo.getX();
                endLastLineY += targetInfo.getY();

                lastLine = new Line2D(startLastLineX, startLastLineY,
                        endLastLineX, endLastLineY);
            } else {
                lastLine = firstLine;
            }

            if (DI_RECTANGLES.contains(targetRefStencilId)) {
                Polyline2D rectangle = createRectangle(targetInfo);

                Collection<Point2D> intersections = rectangle
                        .intersections(lastLine);
                Point2D intersection = intersections.iterator().next();
                graphicInfoList.add(createGraphicInfo(intersection.x(),
                        intersection.y()));
            } else if (DI_CIRCLES.contains(targetRefStencilId)) {
                double targetDockersX = dockersNode.get(dockersNode.size() - 1)
                        .get(EDITOR_BOUNDS_X).doubleValue();
                double targetDockersY = dockersNode.get(dockersNode.size() - 1)
                        .get(EDITOR_BOUNDS_Y).doubleValue();

                Circle2D eventCircle = new Circle2D(targetInfo.getX()
                        + targetDockersX, targetInfo.getY() + targetDockersY,
                        targetDockersX);

                Collection<Point2D> intersections = eventCircle
                        .intersections(lastLine);
                Point2D intersection = intersections.iterator().next();
                graphicInfoList.add(createGraphicInfo(intersection.x(),
                        intersection.y()));
            } else if (DI_GATEWAY.contains(targetRefStencilId)) {
                Polyline2D gatewayRectangle = createGateway(targetInfo);

                Collection<Point2D> intersections = gatewayRectangle
                        .intersections(lastLine);
                Point2D intersection = intersections.iterator().next();
                graphicInfoList.add(createGraphicInfo(intersection.x(),
                        intersection.y()));
            }

            bpmnModel.addFlowGraphicInfoList(edgeId, graphicInfoList);

            // if sequence has a name, just add a label graphic info
            if (!"".equals(edgeNode.get("properties").get("name"))) {
                bpmnModel
                        .addLabelGraphicInfo(edgeId, createGraphicInfo(0D, 0D));
            }
        }
    }

    private Polyline2D createRectangle(GraphicInfo graphicInfo) {
        Polyline2D rectangle = new Polyline2D(new Point2D(graphicInfo.getX(),
                graphicInfo.getY()), new Point2D(graphicInfo.getX()
                + graphicInfo.getWidth(), graphicInfo.getY()), new Point2D(
                graphicInfo.getX() + graphicInfo.getWidth(), graphicInfo.getY()
                        + graphicInfo.getHeight()), new Point2D(
                graphicInfo.getX(), graphicInfo.getY()
                        + graphicInfo.getHeight()), new Point2D(
                graphicInfo.getX(), graphicInfo.getY()));

        return rectangle;
    }

    private Polyline2D createGateway(GraphicInfo graphicInfo) {
        double middleX = graphicInfo.getX() + (graphicInfo.getWidth() / 2);
        double middleY = graphicInfo.getY() + (graphicInfo.getHeight() / 2);

        Polyline2D gatewayRectangle = new Polyline2D(new Point2D(
                graphicInfo.getX(), middleY), new Point2D(middleX,
                graphicInfo.getY()), new Point2D(graphicInfo.getX()
                + graphicInfo.getWidth(), middleY), new Point2D(middleX,
                graphicInfo.getY() + graphicInfo.getHeight()), new Point2D(
                graphicInfo.getX(), middleY));

        return gatewayRectangle;
    }

    private GraphicInfo createGraphicInfo(double x, double y) {
        GraphicInfo graphicInfo = new GraphicInfo();
        graphicInfo.setX(x);
        graphicInfo.setY(y);

        return graphicInfo;
    }
}
